أتقن واصفات خصائص بايثون للخصائص المحسوبة، والتحقق من السمات، وتصميم موجه نحو الكائنات المتقدم. تعلم من خلال الأمثلة العملية وأفضل الممارسات.
واصفات خصائص بايثون: الخصائص المحسوبة ومنطق التحقق
توفر واصفات خصائص بايثون آلية قوية لإدارة الوصول إلى السمات والسلوك داخل الفئات. تسمح لك بتحديد منطق مخصص للحصول على السمات وتعيينها وحذفها، مما يمكّنك من إنشاء خصائص محسوبة، وفرض قواعد التحقق، وتنفيذ أنماط تصميم موجهة نحو الكائنات المتقدمة. يستكشف هذا الدليل الشامل كل ما يتعلق بواصفات الخصائص، مع توفير أمثلة عملية وأفضل الممارسات لمساعدتك على إتقان ميزة بايثون الأساسية هذه.
ما هي واصفات الخصائص؟
في بايثون، الواصف هو سمة كائن لها "سلوك ربط"، مما يعني أنه تم تجاوز الوصول إلى سمته بواسطة الأساليب الموجودة في بروتوكول الواصف. هذه الأساليب هي __get__()
و __set__()
و __delete__()
. إذا تم تعريف أي من هذه الأساليب لسمة، فإنها تصبح واصفًا. واصفات الخصائص، على وجه الخصوص، هي نوع معين من الواصفات المصممة لإدارة الوصول إلى السمات بمنطق مخصص.
الواصفات هي آلية منخفضة المستوى تُستخدم خلف الكواليس بواسطة العديد من ميزات بايثون المضمنة، بما في ذلك الخصائص والأساليب والأساليب الثابتة وأساليب الفئات وحتى super()
. إن فهم الواصفات يمكّنك من كتابة تعليمات برمجية أكثر تطورًا وPythonic.
بروتوكول الواصف
يحدد بروتوكول الواصف الأساليب التي تتحكم في الوصول إلى السمات:
__get__(self, instance, owner)
: يتم استدعاؤه عند استرجاع قيمة الواصف.instance
هو مثيل الفئة التي تحتوي على الواصف، وowner
هو الفئة نفسها. إذا تم الوصول إلى الواصف من الفئة (على سبيل المثال،MyClass.my_descriptor
)، فسيكونinstance
هوNone
.__set__(self, instance, value)
: يتم استدعاؤه عند تعيين قيمة الواصف.instance
هو مثيل الفئة، وvalue
هو القيمة التي يتم تعيينها.__delete__(self, instance)
: يتم استدعاؤه عند حذف سمة الواصف.instance
هو مثيل الفئة.
لإنشاء واصف خاصية، تحتاج إلى تحديد فئة تقوم بتنفيذ واحد على الأقل من هذه الأساليب. لنبدأ بمثال بسيط.
إنشاء واصف خاصية أساسي
فيما يلي مثال أساسي لواصف خاصية يحول سمة إلى أحرف كبيرة:
class UppercaseDescriptor:
def __get__(self, instance, owner):
if instance is None:
return self # إرجاع الواصف نفسه عند الوصول إليه من الفئة
return instance._my_attribute.upper() # الوصول إلى سمة "خاصة"
def __set__(self, instance, value):
instance._my_attribute = value
class MyClass:
my_attribute = UppercaseDescriptor()
def __init__(self, value):
self._my_attribute = value # تهيئة السمة "الخاصة"
# مثال على الاستخدام
obj = MyClass("hello")
print(obj.my_attribute) # الإخراج: HELLO
obj.my_attribute = "world"
print(obj.my_attribute) # الإخراج: WORLD
في هذا المثال:
UppercaseDescriptor
هي فئة واصف تنفذ__get__()
و__set__()
.MyClass
تحدد السمةmy_attribute
وهي مثيل لـUppercaseDescriptor
.- عند الوصول إلى
obj.my_attribute
، يتم استدعاء الأسلوب__get__()
الخاص بـUppercaseDescriptor
، مما يحول_my_attribute
الأساسي إلى أحرف كبيرة. - عند تعيين
obj.my_attribute
، يتم استدعاء الأسلوب__set__()
، وتحديث_my_attribute
الأساسي.
لاحظ استخدام سمة "خاصة" (_my_attribute
). هذه اتفاقية شائعة في بايثون للإشارة إلى أن السمة مخصصة للاستخدام الداخلي داخل الفئة ويجب عدم الوصول إليها مباشرة من الخارج. تمنحنا الواصفات آلية للتوسط في الوصول إلى هذه السمات "الخاصة".
الخصائص المحسوبة
تعتبر واصفات الخصائص ممتازة لإنشاء خصائص محسوبة - السمات التي يتم حساب قيمها ديناميكيًا بناءً على سمات أخرى. يمكن أن يساعد ذلك في الحفاظ على اتساق بياناتك ويمكن أن يجعل التعليمات البرمجية الخاصة بك أكثر قابلية للصيانة. دعنا نفكر في مثال يتضمن تحويل العملات (باستخدام أسعار التحويل الافتراضية للعرض التوضيحي):
class CurrencyConverter:
def __init__(self, usd_to_eur_rate, usd_to_gbp_rate):
self.usd_to_eur_rate = usd_to_eur_rate
self.usd_to_gbp_rate = usd_to_gbp_rate
class Money:
def __init__(self, usd, converter):
self.usd = usd
self.converter = converter
class EURDescriptor:
def __get__(self, instance, owner):
if instance is None:
return self
return instance.usd * instance.converter.usd_to_eur_rate
def __set__(self, instance, value):
raise AttributeError("لا يمكن تعيين EUR مباشرة. عيّن USD بدلاً من ذلك.")
class GBPDescriptor:
def __get__(self, instance, owner):
if instance is None:
return self
return instance.usd * instance.converter.usd_to_gbp_rate
def __set__(self, instance, value):
raise AttributeError("لا يمكن تعيين GBP مباشرة. عيّن USD بدلاً من ذلك.")
eur = EURDescriptor()
gbp = GBPDescriptor()
# مثال على الاستخدام
converter = CurrencyConverter(0.85, 0.75) # أسعار USD إلى EUR و USD إلى GBP
money = Money(100, converter)
print(f"USD: {money.usd}")
print(f"EUR: {money.eur}")
print(f"GBP: {money.gbp}")
# محاولة تعيين EUR أو GBP ستؤدي إلى ظهور AttributeError
# money.eur = 90 # سيؤدي هذا إلى ظهور خطأ
في هذا المثال:
- يحتوي
CurrencyConverter
على أسعار التحويل. - يمثل
Money
مبلغًا من المال بالدولار الأمريكي ولديه مرجع إلى مثيلCurrencyConverter
. EURDescriptor
وGBPDescriptor
هما واصفان يحسبان قيم EUR و GBP بناءً على قيمة USD وأسعار التحويل.- السمات
eur
وgbp
هي أمثلة على هذه الواصفات. - تثير الأساليب
__set__()
AttributeError
لمنع التعديل المباشر لقيم EUR و GBP المحسوبة. يضمن هذا إجراء التغييرات من خلال قيمة USD، مع الحفاظ على الاتساق.
التحقق من السمة
يمكن أيضًا استخدام واصفات الخصائص لفرض قواعد التحقق على قيم السمات. هذا أمر بالغ الأهمية لضمان سلامة البيانات ومنع الأخطاء. لنقم بإنشاء واصف يتحقق من عناوين البريد الإلكتروني. سنحافظ على التحقق بسيطًا لهذا المثال.
import re
class EmailDescriptor:
def __init__(self, attribute_name):
self.attribute_name = attribute_name
def __get__(self, instance, owner):
if instance is None:
return self
return instance.__dict__[self.attribute_name]
def __set__(self, instance, value):
if not self.is_valid_email(value):
raise ValueError(f"عنوان بريد إلكتروني غير صالح: {value}")
instance.__dict__[self.attribute_name] = value
def __delete__(self, instance):
del instance.__dict__[self.attribute_name]
def is_valid_email(self, email):
# تحقق بسيط من البريد الإلكتروني (يمكن تحسينه)
pattern = r"^[\w\.-]+@([\w-]+\.)+[\w-]{2,4}$"
return re.match(pattern, email) is not None
class User:
email = EmailDescriptor("email")
def __init__(self, email):
self.email = email
# مثال على الاستخدام
user = User("test@example.com")
print(user.email)
# محاولة تعيين بريد إلكتروني غير صالح ستؤدي إلى ظهور ValueError
# user.email = "invalid-email" # سيؤدي هذا إلى ظهور خطأ
try:
user.email = "invalid-email"
except ValueError as e:
print(e)
في هذا المثال:
- يتحقق
EmailDescriptor
من عنوان البريد الإلكتروني باستخدام تعبير عادي (is_valid_email
). - يتحقق الأسلوب
__set__()
مما إذا كانت القيمة عنوان بريد إلكتروني صالحًا قبل تعيينه. إذا لم يكن الأمر كذلك، فإنه يثيرValueError
. - تستخدم الفئة
User
EmailDescriptor
لإدارة السمةemail
. - يقوم الواصف بتخزين القيمة مباشرة في
__dict__
الخاص بالمثيل، مما يسمح بالوصول دون تشغيل الواصف مرة أخرى (منع التكرار اللانهائي).
يضمن هذا أنه يمكن فقط تعيين عناوين بريد إلكتروني صالحة للسمة email
، مما يعزز سلامة البيانات. لاحظ أن الدالة is_valid_email
توفر فقط التحقق الأساسي ويمكن تحسينه لإجراء فحوصات أكثر قوة، ربما باستخدام مكتبات خارجية للتحقق من البريد الإلكتروني الدولي إذا لزم الأمر.
استخدام المضمنة property
يوفر بايثون دالة مضمنة تسمى property()
والتي تبسط إنشاء واصفات الخصائص البسيطة. إنها في الأساس عبارة عن غلاف ملائم حول بروتوكول الواصف. غالبًا ما يتم تفضيله للخصائص المحسوبة الأساسية.
class Rectangle:
def __init__(self, width, height):
self._width = width
self._height = height
def get_area(self):
return self._width * self._height
def set_area(self, area):
# قم بتنفيذ المنطق لحساب العرض / الارتفاع من المنطقة
# لتبسيط الأمور، سنقوم فقط بتعيين العرض والارتفاع على الجذر التربيعي
import math
side = math.sqrt(area)
self._width = side
self._height = side
def delete_area(self):
self._width = 0
self._height = 0
area = property(get_area, set_area, delete_area, "مساحة المستطيل")
# مثال على الاستخدام
rect = Rectangle(5, 10)
print(rect.area) # الإخراج: 50
rect.area = 100
print(rect._width) # الإخراج: 10.0
print(rect._height) # الإخراج: 10.0
del rect.area
print(rect._width) # الإخراج: 0
print(rect._height) # الإخراج: 0
في هذا المثال:
- تأخذ
property()
ما يصل إلى أربعة وسيطات:fget
(الحاجز)،fset
(المُعيِّن)،fdel
(الحذف)، وdoc
(سلسلة التوثيق). - نحدد أساليب منفصلة للحصول على
area
وتعيينها وحذفها. - تقوم
property()
بإنشاء واصف خاصية يستخدم هذه الأساليب لإدارة الوصول إلى السمات.
غالبًا ما يكون المضمنة property
أكثر قابلية للقراءة وإيجازًا في الحالات البسيطة من إنشاء فئة واصف منفصلة. ومع ذلك، بالنسبة للمنطق الأكثر تعقيدًا أو عندما تحتاج إلى إعادة استخدام منطق الواصف عبر سمات أو فئات متعددة، فإن إنشاء فئة واصف مخصصة يوفر تنظيمًا وإعادة استخدام أفضل.
متى تستخدم واصفات الخصائص
تعد واصفات الخصائص أداة قوية، ولكن يجب استخدامها بحكمة. فيما يلي بعض السيناريوهات التي تكون فيها مفيدة بشكل خاص:
- الخصائص المحسوبة: عندما تعتمد قيمة السمة على سمات أخرى أو عوامل خارجية وتحتاج إلى حسابها ديناميكيًا.
- التحقق من السمة: عندما تحتاج إلى فرض قواعد أو قيود معينة على قيم السمات للحفاظ على سلامة البيانات.
- تغليف البيانات: عندما تريد التحكم في كيفية الوصول إلى السمات وتعديلها، وإخفاء تفاصيل التنفيذ الأساسية.
- سمات للقراءة فقط: عندما تريد منع تعديل السمة بعد تهيئتها (عن طريق تعريف أسلوب
__get__
فقط). - التحميل الكسول: عندما تريد تحميل قيمة السمة فقط عند الوصول إليها لأول مرة (على سبيل المثال، تحميل البيانات من قاعدة بيانات).
- التكامل مع الأنظمة الخارجية: يمكن استخدام الواصفات كطبقة تجريد بين كائنك ونظام خارجي مثل قاعدة البيانات / واجهة برمجة التطبيقات (API) حتى لا يضطر تطبيقك إلى القلق بشأن التمثيل الأساسي. هذا يزيد من قابلية نقل تطبيقك. تخيل أن لديك خاصية تخزن تاريخًا، ولكن التخزين الأساسي قد يكون مختلفًا بناءً على النظام الأساسي، يمكنك استخدام الواصف لتجريد هذا بعيدًا.
ومع ذلك، تجنب استخدام واصفات الخصائص بشكل غير ضروري، لأنها قد تضيف تعقيدًا إلى التعليمات البرمجية الخاصة بك. للوصول البسيط إلى السمات بدون أي منطق خاص، غالبًا ما يكون الوصول المباشر إلى السمة كافيًا. يمكن أن يؤدي الإفراط في استخدام الواصفات إلى جعل التعليمات البرمجية الخاصة بك أكثر صعوبة في الفهم والصيانة.
أفضل الممارسات
فيما يلي بعض أفضل الممارسات التي يجب وضعها في الاعتبار عند العمل مع واصفات الخصائص:
- استخدام السمات "الخاصة": قم بتخزين البيانات الأساسية في سمات "خاصة" (على سبيل المثال،
_my_attribute
) لتجنب تعارضات التسمية ومنع الوصول المباشر من خارج الفئة. - التعامل مع
instance is None
: في الأسلوب__get__()
، تعامل مع الحالة التي يكون فيهاinstance
هوNone
، والتي تحدث عندما يتم الوصول إلى الواصف من الفئة نفسها بدلاً من المثيل. أعد كائن الواصف نفسه في هذه الحالة. - إثارة الاستثناءات المناسبة: عند فشل التحقق أو عند عدم السماح بتعيين السمة، قم بإثارة الاستثناءات المناسبة (على سبيل المثال،
ValueError
،TypeError
،AttributeError
). - توثيق الواصفات الخاصة بك: أضف سلاسل التوثيق إلى فئات الواصفات والخصائص الخاصة بك لشرح الغرض منها واستخدامها.
- ضع في اعتبارك الأداء: يمكن أن يؤثر منطق الواصف المعقد على الأداء. قم بتوصيف التعليمات البرمجية الخاصة بك لتحديد أي عنق زجاجة في الأداء وتحسين الواصفات الخاصة بك وفقًا لذلك.
- اختر النهج الصحيح: حدد ما إذا كنت تريد استخدام المضمنة
property
أو فئة واصف مخصصة بناءً على مدى تعقيد المنطق والحاجة إلى إعادة الاستخدام. - اجعل الأمر بسيطًا: تمامًا مثل أي تعليمات برمجية أخرى، يجب تجنب التعقيد. يجب أن تعمل الواصفات على تحسين جودة تصميمك، وليس إخفاءه.
تقنيات الواصف المتقدمة
بالإضافة إلى الأساسيات، يمكن استخدام واصفات الخصائص لتقنيات أكثر تقدمًا:
- واصفات غير البيانات: الواصفات التي تحدد فقط الأسلوب
__get__()
تسمى واصفات غير البيانات (أو تسمى أحيانًا واصفات "التظليل"). لها أولوية أقل من سمات المثيل. إذا كانت هناك سمة مثيل بنفس الاسم، فستظلل الواصف غير البيانات. يمكن أن يكون هذا مفيدًا لتوفير قيم افتراضية أو سلوك التحميل الكسول. - واصفات البيانات: تسمى الواصفات التي تحدد
__set__()
أو__delete__()
واصفات البيانات. لها أولوية أعلى من سمات المثيل. سيؤدي الوصول إلى السمة أو تعيينها دائمًا إلى تشغيل أساليب الواصف. - الجمع بين الواصفات: يمكنك الجمع بين واصفات متعددة لإنشاء سلوك أكثر تعقيدًا. على سبيل المثال، يمكنك الحصول على واصف يتحقق من سمة ويحولها في نفس الوقت.
- الطبقات الفوقية: تتفاعل الواصفات بقوة مع الطبقات الفوقية، حيث يتم تعيين الخصائص بواسطة الطبقة الفوقية ويتم توريثها بواسطة الفئات التي تنشئها. يتيح هذا تصميمًا قويًا للغاية، مما يجعل الواصفات قابلة لإعادة الاستخدام عبر الفئات، وحتى أتمتة تعيين الواصفات بناءً على بيانات التعريف.
اعتبارات عالمية
عند التصميم باستخدام واصفات الخصائص، خاصة في سياق عالمي، ضع في اعتبارك ما يلي:
- الترجمة: إذا كنت تتحقق من صحة البيانات التي تعتمد على الإعدادات المحلية (على سبيل المثال، الرموز البريدية، وأرقام الهواتف)، فاستخدم المكتبات المناسبة التي تدعم المناطق والتنسيقات المختلفة.
- المناطق الزمنية: عند العمل مع التواريخ والأوقات، ضع في اعتبارك المناطق الزمنية واستخدم مكتبات مثل
pytz
للتعامل مع التحويلات بشكل صحيح. - العملة: إذا كنت تتعامل مع قيم العملات، فاستخدم المكتبات التي تدعم العملات المختلفة وأسعار الصرف. ضع في اعتبارك استخدام تنسيق عملة قياسي.
- ترميز الأحرف: تأكد من أن التعليمات البرمجية الخاصة بك تتعامل مع ترميزات الأحرف المختلفة بشكل صحيح، خاصة عند التحقق من صحة السلاسل.
- معايير التحقق من صحة البيانات: تحتوي بعض المناطق على متطلبات قانونية أو تنظيمية محددة للتحقق من صحة البيانات. كن على علم بها وتأكد من أن الواصفات الخاصة بك تتوافق معها.
- إمكانية الوصول: يجب تصميم الخصائص بطريقة تسمح لتطبيقك بالتكيف مع اللغات والثقافات المختلفة دون تغيير التصميم الأساسي.
الخلاصة
تعد واصفات خصائص بايثون أداة قوية ومتعددة الاستخدامات لإدارة الوصول إلى السمات والسلوك. يسمح لك بإنشاء خصائص محسوبة وفرض قواعد التحقق وتنفيذ أنماط تصميم موجهة نحو الكائنات المتقدمة. من خلال فهم بروتوكول الواصف واتباع أفضل الممارسات، يمكنك كتابة تعليمات برمجية بايثون أكثر تطورًا وقابلة للصيانة.
من ضمان سلامة البيانات من خلال التحقق من الصحة إلى حساب القيم المشتقة عند الطلب، توفر واصفات الخصائص طريقة أنيقة لتخصيص معالجة السمات في فئات بايثون الخاصة بك. يفتح إتقان هذه الميزة فهمًا أعمق لنموذج كائن بايثون ويمكّنك من إنشاء تطبيقات أكثر قوة ومرونة.
باستخدام property
أو الواصفات المخصصة، يمكنك تحسين مهارات بايثون الخاصة بك بشكل كبير.